// Copyright (C) 2020 sanfusu@foxmail.com
//
// This file is part of rust_elf.
//
// rust_elf is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// rust_elf is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with rust_elf.  If not, see <http://www.gnu.org/licenses/>.
#![no_std]
#![allow(dead_code)]

#[cfg(test)]
extern crate std;

use align::{AlignedRef, AlignedSlice};
use header::{
    ident::{FileClass, Ident},
    Header,
};

macro_rules! impl_borrow {
    ($($Type:ty),+) => {
        $(
            impl core::borrow::Borrow<[u8; core::mem::size_of::<$Type>()]> for $Type {
                fn borrow(&self) -> &[u8; core::mem::size_of::<$Type>()] {
                    use core::{convert::TryInto, ptr::slice_from_raw_parts};
                    unsafe {
                        &*slice_from_raw_parts(
                            self as *const $Type as *const u8,
                            core::mem::size_of::<$Type>(),
                        )
                    }
                    .try_into()
                    .unwrap() // 不会 panic，因为长度一致。
                }
            }
            impl core::borrow::Borrow<[u8]> for $Type {
                fn borrow(&self) -> &[u8] {
                    use core::ptr::slice_from_raw_parts;
                    unsafe {
                        &*slice_from_raw_parts(
                            self as *const $Type as *const u8,
                            core::mem::size_of::<$Type>(),
                        )
                    }
                }
            }
        )+
    };
}

/// 定义透明结构体，并定义一些基本方法。
/// 附加和其他子类型的转换关系。
///
/// ```
/// trans! {
///     ProcSecType: u32 {
///         as_sec -> SecType
///     },
///     EnvSecType: u32,
///     BasicSecType: u32 {
///         as_sec -> SecType
///     },
///
///     #[derive(Default)]
///     SectionFlag: u64,
/// }
/// ```
macro_rules! trans {
    (
        $(
            $(#[$Att:meta])*
            $Struct:ident: $FieldType:ty $({
                $(
                    $(::$Variance:ident($VarianceType:ty))? $AsFn:ident -> $Target:ident
                ),+ $(,)?
            })?
        ),+ $(,)?
    ) => {
        $(
            #[repr(transparent)]
            #[derive(PartialEq)]
            $(#[$Att])*
            pub struct $Struct {
                pub(crate) data: $FieldType,
            }

            impl $Struct {
                #[inline]
                pub const unsafe fn from_raw(data: $FieldType) -> $Struct {
                    $Struct {
                        data
                    }
                }
                #[inline]
                pub fn raw(&self) -> $FieldType {
                    self.data
                }
                $(
                    $(
                        #[inline]
                        pub fn $AsFn(&self)->$Target {
                            $Target {
                                data: self.data
                            }
                        }
                    )+
                )?
            }
        )+
    };
}

macro_rules! def_const {
    ($(
        $Struct:path {
            $(
                $(#[$Attr:meta])*
                $CosntName:ident:$ConstValue:literal
            ),+ $(,)?
        }
    )+) => {
        $(
            impl $Struct {
                $(
                    $(#[$Attr])*
                    pub const $CosntName:$Struct = { $Struct {data:$ConstValue} };
                )+
            }
        )+
    };
}

pub mod arch;
pub mod header;
pub mod program;
pub mod section;

pub type Elf64Addr = u64;
pub type Elf64Off = u64;
pub type Elf64Half = u16;
pub type Elf64Word = u32;
pub type Elf64Sword = u32;
pub type Elf64Xword = u64;
pub type Elf64Sxword = i64;

pub fn hash(name: &[u8]) -> u32 {
    let mut h = 0;
    name.iter().for_each(|&x| {
        h = (h << 4) + (x as u32);
        let g = h & 0xf0_00_00_00;
        if g != 0 {
            h ^= g >> 24;
        }
        h &= 0x0f_ff_ff_ff;
    });
    h
}

#[derive(Debug)]
pub enum ElfParserError {
    InvalidIdentMagic,
    InvalidIdentClass,
    MissAligned,
    InvalidLength,
}
pub struct Elf<'a> {
    src: &'a [u8],
}

impl<'a> Elf<'a> {
    pub fn parse(src: &'a [u8]) -> Option<Self> {
        let ident = AlignedRef::<Ident>::from_bytes(src)?;

        if ident.magic != Ident::MAGIC {
            return None;
        }
        if ident.file_class != FileClass::ELFCLASS64 {
            return None;
        }

        Some(Elf { src })
    }
    pub fn ident(&'a self) -> Option<&'a Ident> {
        AlignedRef::<Ident>::from_bytes(self.src)
    }
    pub fn header(&'a self) -> Option<&'a Header> {
        AlignedRef::<Header>::from_bytes(self.src)
    }
    pub fn pht(&'a self, header: &Header) -> Option<&'a [program::Header]> {
        let table_slice = self.src.get(header.pht_range())?;
        let len = header.phnum as usize;
        AlignedSlice::<program::Header>::from_bytes(table_slice, len)
    }
    pub fn sht(&'a self, header: &Header) -> Option<&'a [section::Header]> {
        let len = header.shnum as usize;
        let table_slice = self.src.get(header.sht_range())?;
        AlignedSlice::<section::Header>::from_bytes(table_slice, len)
    }
}

#[cfg(test)]
mod test {
    use core::borrow::Borrow;
    use std::println;

    use crate::{
        header::ident::{self, Ident},
        Elf,
    };

    #[test]
    fn ident_test() {
        let ident = Ident {
            magic: Ident::MAGIC,
            file_class: ident::FileClass::ELFCLASS64,
            ..Default::default()
        };
        let arr: &[u8; core::mem::size_of::<Ident>()] = ident.borrow();
        println!("{:#?}", Elf::parse(arr).unwrap().ident().as_ref());
    }
}
